﻿// HSS.IO.Zip.ZipCrypto.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       ZipCrypto.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.IO.Zip
{
	#region Using Directives
	using System;
	#endregion

	#region ZipCrypto
	/// <summary> 
	/// This class implements the "traditional" or "classic" PKZip encryption,
	/// which today is considered to be weak. On the other hand it is
	/// ubiquitous. This class is intended for use only by the this Zip library.
	/// </summary>
	/// <remarks>
	/// Most uses of the this library will not involve direct calls into the
	/// ZipCrypto class.  Instead, the ZipCrypto class is instantiated and used by
	/// the ZipEntry() class when encryption or decryption on an entry is employed.
	/// If for some reason you really wanted to use a weak encryption algorithm
	/// in some other application, you might use this library.  But you would be much
	/// better off using one of the built-in strong encryption libraries in the 
	/// .NET Framework, like the AES algorithm or SHA. 
	/// </remarks>
	public class ZipCrypto
	{
		#region Fields
		private UInt32[] _Keys = { 0x12345678, 0x23456789, 0x34567890 };
		private CRC32 crc32 = new CRC32();
		#endregion

		#region Constructor
		/// <summary>
		/// The default constructor.  You're probably never gonna call this.  Seriously.
		/// Stop reading this documentation.  It's not useful to you.  Go do something else.
		/// Check the football scores. Go get an ice cream with a friend.  Seriously.
		/// </summary>
		public ZipCrypto() { }
		#endregion

		#region Properties
		/// <summary> 
		/// From AppNote.txt:
		/// unsigned char decrypt_byte()
		///     local unsigned short temp
		///     temp :=- Key(2) | 2
		///     decrypt_byte := (temp * (temp ^ 1)) bitshift-right 8
		/// end decrypt_byte
		/// </summary>		
		private byte MagicByte
		{
			get
			{
				UInt16 t = (UInt16)((UInt16)(_Keys[2] & 0xFFFF) | 2);
				return (byte)((t * (t ^ 1)) >> 8);
			}
		}
		#endregion

		#region Methods
		/// <summary> 
		/// Call this method on a cipher text to render the plaintext. You must
		/// first initialize the cipher with a call to InitCipher.
		/// </summary>		
		/// <example>
		/// <code>
		/// var cipher = new ZipCrypto();
		/// cipher.InitCipher(Password);
		/// // Decrypt the header.  This has a side effect of "further initializing the
		/// // encryption keys" in the traditional zip encryption. 
		/// byte[] DecryptedMessage = cipher.DecryptMessage(EncryptedMessage);
		/// </code>
		/// </example>
		/// <param name="cipherText">The encrypted buffer.</param>
		/// <param name="length">
		/// The number of bytes to encrypt.  
		/// Should be less than or equal to CipherText.Length.
		/// </param>
		/// <returns>The plaintext.</returns>
		public byte[] DecryptMessage(byte[] cipherText, int length)
		{

			if (cipherText == null)
				throw new ZipException("Cannot decrypt.", new System.ArgumentException("Bad length during Decryption: cipherText must be non-null.", "cipherText"));
			if (length > cipherText.Length)
				throw new ZipException("Cannot decrypt.", new System.ArgumentException("Bad length during Decryption: the length parameter must be smaller than or equal to the size of the destination array.", "length"));

			byte[] PlainText = new byte[length];
			for (int i = 0; i < length; i++)
			{
				byte C = (byte)(cipherText[i] ^ MagicByte);
				UpdateKeys(C);
				PlainText[i] = C;
			}
			return PlainText;
		}
		/// <summary>
		/// This is the converse of DecryptMessage.  It encrypts the plaintext
		/// and produces a ciphertext. 
		/// </summary>
		/// <param name="plaintext">The plain text buffer.</param>
		/// <param name="length">
		/// The number of bytes to encrypt.  
		/// Should be less than or equal to PlainText.Length.
		/// </param>
		/// <returns>The ciphertext.</returns>
		public byte[] EncryptMessage(byte[] plaintext, int length)
		{
			if (plaintext == null)
				throw new ZipException("Cannot encrypt.", new System.ArgumentException("Bad length during Encryption: the plainText must be non-null.", "plaintext"));

			if (length > plaintext.Length)
				throw new ZipException("Cannot encrypt.", new System.ArgumentException("Bad length during Encryption: The length parameter must be smaller than or equal to the size of the destination array.", "length"));

			byte[] CipherText = new byte[length];
			for (int i = 0; i < length; i++)
			{
				byte C = plaintext[i];
				CipherText[i] = (byte)(plaintext[i] ^ MagicByte);
				UpdateKeys(C);
			}
			return CipherText;
		}
		/// <summary>
		/// This initializes the cipher with the given password. 
		/// See AppNote.txt for details. 
		/// </summary>
		/// <param name="passphrase">The passphrase for encrypting or decrypting with this cipher.
		/// </param>
		/// <remarks>
		/// <code>
		/// Step 1 - Initializing the encryption keys
		/// -----------------------------------------
		/// Start with these keys:        
		/// Key(0) := 305419896 (0x12345678)
		/// Key(1) := 591751049 (0x23456789)
		/// Key(2) := 878082192 (0x34567890)
		/// 
		/// Then, initialize the keys with a password:
		/// 
		/// loop for i from 0 to length(password)-1
		///     update_keys(password(i))
		/// end loop
		/// 
		/// Where update_keys() is defined as:
		/// 
		/// update_keys(char):
		///   Key(0) := crc32(key(0),char)
		///   Key(1) := Key(1) + (Key(0) bitwiseAND 000000ffH)
		///   Key(1) := Key(1) * 134775813 + 1
		///   Key(2) := crc32(key(2),key(1) rightshift 24)
		/// end update_keys
		/// 
		/// Where crc32(old_crc,char) is a routine that given a CRC value and a
		/// character, returns an updated CRC value after applying the CRC-32
		/// algorithm described elsewhere in this document.
		///
		/// </code>
		/// <para>
		/// After the keys are initialized, then you can use the cipher to encrypt
		/// the plaintext. 
		/// </para>
		/// <para>
		/// Essentially we encrypt the password with the keys, then discard the 
		/// ciphertext for the password. This initializes the keys for later use.
		/// </para>
		/// </remarks>
		public void InitCipher(string passphrase)
		{
			byte[] p = SharedUtilities.StringToByteArray(passphrase);
			for (int i = 0; i < passphrase.Length; i++)
				UpdateKeys(p[i]);
		}
		/// <summary>
		/// UpdateKeys
		/// </summary>
		/// <param name="byeValue">byte</param>
		private void UpdateKeys(byte byeValue)
		{
			_Keys[0] = (UInt32)crc32.ComputeCrc32(_Keys[0], byeValue);
			_Keys[1] = _Keys[1] + (byte)_Keys[0];
			_Keys[1] = _Keys[1] * 0x08088405 + 1;
			_Keys[2] = (UInt32)crc32.ComputeCrc32(_Keys[2], (byte)(_Keys[1] >> 24));
		}
		#endregion
	}
	#endregion

	#region CryptoMode
	internal enum CryptoMode
	{
		Encrypt,
		Decrypt
	}
	#endregion

	#region ZipCipherStream
	/// <summary>
	/// A Stream for reading and concurrently decrypting data from a zip file, 
	/// or for writing and concurrently encrypting data to a zip file.
	/// </summary>
	internal class ZipCipherStream : System.IO.Stream
	{
		#region Fields
		private ZipCrypto _cipher;
		private System.IO.Stream _s;
		private CryptoMode _mode;
		#endregion

		#region Constructor
		/// <summary>
		/// The  constructor.
		/// </summary>
		/// <param name="s">The underlying stream</param>
		/// <param name="mode">To either encrypt or decrypt.</param>
		/// <param name="cipher">The pre-initialized ZipCrypto object.</param>
		public ZipCipherStream(System.IO.Stream s, ZipCrypto cipher, CryptoMode mode)
			: base()
		{
			_cipher = cipher;
			_s = s;
			_mode = mode;
		}
		#endregion

		#region Overrides
		public override int Read(byte[] buffer, int offset, int count)
		{
			if (_mode == CryptoMode.Encrypt)
				throw new NotImplementedException();

			byte[] db = new byte[count];
			int n = _s.Read(db, 0, count);
			byte[] decrypted = _cipher.DecryptMessage(db, n);
			for (int i = 0; i < n; i++)
			{
				buffer[offset + i] = decrypted[i];
			}
			return n;
		}
		public override void Write(byte[] buffer, int offset, int count)
		{
			if (_mode == CryptoMode.Decrypt)
				throw new NotImplementedException();

			byte[] plaintext = null;
			if (offset != 0)
			{
				plaintext = new byte[count];
				for (int i = 0; i < count; i++)
				{
					plaintext[i] = buffer[offset + i];
				}
			}
			else plaintext = buffer;

			byte[] encrypted = _cipher.EncryptMessage(plaintext, count);
			_s.Write(encrypted, 0, encrypted.Length);
		}
		public override bool CanRead
		{
			get { return (_mode == CryptoMode.Decrypt); }
		}
		public override bool CanSeek
		{
			get { return false; }
		}
		public override bool CanWrite
		{
			get { return (_mode == CryptoMode.Encrypt); }
		}
		public override void Flush()
		{
			throw new NotImplementedException();
		}
		public override long Length
		{
			get { throw new NotImplementedException(); }
		}
		public override long Position
		{
			get { throw new NotImplementedException(); }
			set { throw new NotImplementedException(); }
		}
		public override long Seek(long offset, System.IO.SeekOrigin origin)
		{
			throw new NotImplementedException();
		}
		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}
		#endregion
	}
	#endregion
}